package com.such.kit.file;

import java.io.File;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.such.kit.Callable;
import com.such.kit.Logger;
import com.such.kit.file.bean.FileInfo;
import com.such.kit.file.support.FileFilter;
import com.such.kit.string.StringCommons;
import com.such.kit.validate.ValidateCommons;

/**
 * @author SUCH
 * <pre>
 * jar 文件处理工具类
 * </pre>
 */
public class JarFileCommons {

	/**
	 * <pre>
	 * 获取 classes 根目录路径
	 * </pre>
	 * @return classes 根目录路径
	 */
	public static FileInfo getClassesPath() {
		URL url = getClassLoader().getResource("");
		if (ValidateCommons.isEmpty(url)) {
			throw new RuntimeException("未找到 classes 根目录");
		}
		URI uri = null;
		try {
			uri = url.toURI();
		} catch (URISyntaxException e) {
			throw new RuntimeException(e);
		}
		return new FileInfo(new File(uri));
	}

	/**
	 * <pre>
	 * 获取相对（classes）路径的绝对路径
	 * 
	 * [举例] {@link JarFileCommons} 的 relativePath 为 com/such/kit/file/JarFileCommons.class
	 * </pre>
	 * @param relativePath 相对（classes）路径
	 * @return 绝对路径
	 */
	public static FileInfo getAbsolutePath(String relativePath) {
		URL url = getClassLoader().getResource(relativePath);
		if (ValidateCommons.isEmpty(url)) {
			Logger.warn(JarFileCommons.class, StringCommons.merger("未找到 [", relativePath, "] 的绝对路径"));
			return null;
		}
		String protocol = url.getProtocol();
		if ("jar".equals(protocol)) {
			// file:/D:/SUCH/Works/such/kit/target/kit-0.0.1.jar!/com/such/kit/database/dialect/configs/oracle.properties
			String path = url.getPath();
			try {
				path = URLDecoder.decode(path, StringCommons.CHARSET_UTF8);
			} catch (UnsupportedEncodingException e) {
				throw new RuntimeException(e);
			}
			String[] pathArray = path.split("!");
			int len = pathArray.length;
			if (len > 2) {
				/*
				 * 需要注意的是，某些特殊的打包方式中，jar 包的根目录并不是 classes
				 * 此时 url 可能是多个 ! 分隔的多部分
				 * 如 file:/D:/SUCH/Works/such/kit/target/kit-0.0.1.jar!/BOOT-INF/classes!/com/such/kit/database/dialect/configs/oracle.properties
				 * 在这个 url 中，/BOOT-INF/classes 为中间路径
				 * jar 包中读取文件一般使用相对路径 com/such/kit/database/dialect/configs/oracle.properties
				 * 从 JarFileCommons 的定位来看，其主要处理 jar 包中文件的读取
				 * 因此按 ! 拆分后，第 1 部分为打包路径，其它部分为打包项
				 */
				String packagePath = pathArray[0].substring(5); // 取第 1 部分为打包路径
				String entryName = StringCommons.merger(Arrays.copyOfRange(pathArray, 1, len)).substring(1); // 其它部分打包项
				Logger.debug(JarFileCommons.class, StringCommons.merger("从 jar 中读取到 url [", path, "] 打包路径 [", packagePath, "] 打包项 [", entryName, "]"));
				return new FileInfo(packagePath, entryName);
			} else {
				String packagePath = pathArray[0].substring(5); // 取第 1 部分为打包路径
				String entryName = pathArray[len - 1].substring(1); // 其它部分打包项
				return new FileInfo(packagePath, entryName);
			}
		} else {
			URI uri = null;
			try {
				uri = url.toURI();
			} catch (URISyntaxException e) {
				throw new RuntimeException(e);
			}
			return new FileInfo(new File(uri));
		}
	}

	/**
	 * <pre>
	 * 获取类的绝对路径
	 * </pre>
	 * @param clazz 类
	 * @return 类的绝对路径
	 */
	public static FileInfo getClassAbsolutePath(Class<?> clazz) {
		String relativePath = getClassRelativePath(clazz);
		return getAbsolutePath(relativePath);
	}

	/**
	 * <pre>
	 * 获取类相对 classpath 的路径
	 * </pre>
	 * @param clazz 类
	 * @return 相对 classpath 的路径
	 */
	public static String getClassRelativePath(Class<?> clazz) {
		return StringCommons.merger(clazz.getName().replace(".", FileCommons.COMPAT_SEPARATOR), ".class");
	}

	/**
	 * <pre>
	 * 获取指定资源绝对路径
	 * 
	 * [举例] {@link JarFileCommons} 的 relativePath 为 com/such/kit/file/JarFileCommons.class
	 * </pre>
	 * @param relativeFilePath 相对（classes）路径
	 * @return 绝对路径
	 */
	public static String getResource(String relativeFilePath) {
		Logger.debug(JarFileCommons.class, StringCommons.merger("待处理路径：", relativeFilePath));
		URL url = getClassLoader().getResource(relativeFilePath);
		String absolutePath = url.getPath();
		Logger.debug(JarFileCommons.class, StringCommons.merger("绝对路径：", absolutePath));
		String protocol = url.getProtocol();
		if (absolutePath.startsWith(protocol)) {
			absolutePath = absolutePath.substring(protocol.length());
		}
		String fileProtocol = "file:/";
		if (absolutePath.startsWith(fileProtocol)) {
			absolutePath = absolutePath.substring(fileProtocol.length());
		}
		Logger.debug(JarFileCommons.class, StringCommons.merger("处理后路径：", absolutePath));
		return absolutePath;
	}

	/**
	 * <pre>
	 * 获取指定资源输入流
	 * 
	 * [举例] {@link JarFileCommons} 的 relativePath 为 com/such/kit/file/JarFileCommons.class
	 * </pre>
	 * @param relativeFilePath 相对（classes）路径
	 * @return 输入流
	 */
	public static InputStream getResourceStream(String relativeFilePath) {
		return getClassLoader().getResourceAsStream(relativeFilePath);
	}

	/**
	 * <pre>
	 * [参考 {@link #listFolder(String, FileFilter)}]
	 * 
	 * fileFilter 的默认值为 null
	 * </pre>
	 */
	public static List<FileInfo> listFolder(String relativeFolderPath) 
			throws Exception {
		return listFolder(relativeFolderPath, new FileFilter() {
			@Override
			public boolean accept(FileInfo fileInfo) {
				return true;
			}
		});
	}

	/**
	 * <pre>
	 * 列表文件夹
	 * 级联列表文件夹下的文件、文件夹
	 * </pre>
	 * @param relativeFolderPath 文件夹相对（classes）路径
	 * @param fileFilter 文件列表过滤器 [可空 为空时不做过滤]
	 * @return 文件列表
	 */
	public static List<FileInfo> listFolder(String relativeFolderPath, FileFilter fileFilter) 
			throws Exception {
		final List<FileInfo> fileInfos = new ArrayList<FileInfo>();
		listFolder(relativeFolderPath, fileFilter, new Callable<FileInfo, Void>() {
			@Override
			public Void call(FileInfo fileInfo) throws Exception {
				fileInfos.add(fileInfo);
				return null;
			}
		});
		return fileInfos;
	}

	/**
	 * <pre>
	 * [参考 {@link #listFolder(String, FileFilter, Callable)}]
	 * 
	 * fileFilter 的默认值为 null
	 * </pre>
	 */
	public static void listFolder(String relativeFolderPath, Callable<FileInfo, Void> processor) 
			throws Exception {
		listFolder(relativeFolderPath, null, processor);
	}

	/**
	 * <pre>
	 * 列表文件夹
	 * 级联列表文件夹下的文件、文件夹
	 * </pre>
	 * @param relativeFolderPath 文件夹相对（classes）路径
	 * @param fileFilter 文件列表过滤器 [可空 为空时不做过滤]
	 * @param processor 文件处理回调
	 * @throws Exception
	 */
	public static void listFolder(final String relativeFolderPath, final FileFilter fileFilter, Callable<FileInfo, Void> processor) 
			throws Exception {
		FileInfo fileInfo = getAbsolutePath(relativeFolderPath);
		if (fileInfo.isEntry()) {
			FileInfo packageFile = fileInfo.getPackageFile();
			ZipFileCommons.listZip(packageFile.getPath(), StringCommons.CHARSET_UTF8, new FileFilter() {
				@Override
				public boolean accept(FileInfo fileInfo) {
					String path = fileInfo.getPath();
					if (!path.equals(relativeFolderPath) && path.startsWith(relativeFolderPath)) {
						if (ValidateCommons.isEmpty(fileFilter) || fileFilter.accept(fileInfo)) {
							return true;
						}
					}
					return false;
				}
			}, processor);
		} else {
			FileCommons.listFolder(fileInfo.getPath(), fileFilter, processor);
		}
	}

	private static ClassLoader getClassLoader() {
		return Thread.currentThread().getContextClassLoader();
	}

}
